use std::collections::BTreeMap;

use gpio::{create_crh, create_crl, create_odr, create_other, parse_pin_config, PinConfig};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};



mod gpio;
mod dma;
mod adc;

/**
 * 
#[io_group(
    TX_PA9_AF_PP,
    RX_PA10_IN_FLOAT,
    DC_PA1_ANALOG,
    D1_PC1_OUT_PP,
    D2_PA2_OUT_OD
)]
 */
#[proc_macro_attribute]
pub fn io_group(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let name = &input.ident;
    let attr_str = attr.to_string();
    let ios: Vec<&str> = attr_str.split(",").collect();
    let mut map: BTreeMap<&str, Vec<PinConfig>> = BTreeMap::new();

    for io in ios {
        let config = parse_pin_config(io);
        let vec = map.get_mut(config.pin.name);
        if let Some(vec) = vec {
            vec.push(config);
        } else {
            let mut vec = Vec::new();
            let name = config.pin.name;
            vec.push(config);
            map.insert(name, vec);
        }
    }

    let mut tokens = Vec::new();
    let mut others = Vec::new();
    let mut rccs = Vec::new();
    for (k, v) in map.iter() {
        tokens.push(create_crl(k, v));
        tokens.push(create_crh(k, v));
        tokens.push(create_odr(k, v));
        let other = create_other(v);
        others.push( other );
        rccs.push( format_ident!("iop{}en", k.to_lowercase()) );
    }

    let expand = quote! {
        #input

        impl #name {
            
            #[inline(always)]
            pub fn init(&self) {
                RCC.apb2enr.modify(|_, w| {
                    w #(.#rccs().enabled())*
                });

                #(#tokens)*
            }

            #(#others)*
        } 
    };
    TokenStream::from(expand)
}

// #[dma(DMA1_CH5_SIZE8)]
#[proc_macro_attribute]
pub fn dma(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let name = &input.ident;
    let attr = attr.to_string();
    let config = dma::parse(&attr);
  
    let dma = format_ident!("DMA{}", config.dma);
    let cgif = format_ident!("cgif{}", config.ch);
    let ctcif = format_ident!("ctcif{}", config.ch);
    let chtif = format_ident!("chtif{}", config.ch);
    let cteif = format_ident!("cteif{}", config.ch);

    let tcif = format_ident!("tcif{}", config.ch);
    let rcc = format_ident!("dma{}en", config.dma);

    let size_type = format_ident!("u{}", config.size);
    let ch = format_ident!("ch{}", config.ch);
    let size_bit = format_ident!("bits{}", config.size);

    let expand = quote! {
        #input

        impl #name {

            #[inline(always)]
            pub unsafe fn read_with_circ(&self, periph_addr: u32, buf: &mut [#size_type]) {
                #dma.#ch.ndtr.write(|w| {
                    w.ndt().bits(buf.len() as u16)
                });
                #dma.#ch.mar.write(|w| {
                    w.ma().variant(buf.as_ptr() as u32)
                });
                #dma.#ch.par.write(|w| {
                    w.pa().variant(periph_addr)
                });
                #dma.#ch.cr.write(|w| {
                    w.mem2mem().disabled()
                    .pl().low()
                    .msize().#size_bit()
                    .psize().#size_bit()
                    .minc().enabled()
                    .pinc().disabled()
                    .circ().enabled()
                    .dir().from_peripheral()
                    .teie().disabled()
                    .htie().disabled()
                    .tcie().disabled()
                    .en().enabled()
                });
            }

            #[inline(always)]
            pub unsafe fn write_with_it(&self, periph_addr: u32, buf: &[#size_type]) {
                #dma.#ch.cr.reset();
                #dma.#ch.ndtr.write(|w| {
                    w.ndt().bits(buf.len() as u16)
                });
                #dma.#ch.mar.write(|w| {
                    w.ma().variant(buf.as_ptr() as u32)
                });
                #dma.#ch.par.write(|w| {
                    w.pa().variant(periph_addr)
                });
                #dma.#ch.cr.write(|w| {
                    w.mem2mem().disabled()
                    .pl().low()
                    .msize().#size_bit()
                    .psize().#size_bit()
                    .minc().enabled()
                    .pinc().disabled()
                    .circ().disabled()
                    .dir().from_memory()
                    .teie().disabled()
                    .htie().disabled()
                    .tcie().enabled()
                    .en().enabled()
                });
            }

            #[inline(always)]
            pub fn init_rcc(&self) {
                RCC.ahbenr.modify(|_, w| {
                    w.#rcc().enabled()
                });
                #dma.#ch.cr.reset();
            }

            #[inline(always)]
            pub fn clear_flags(&self) {
                #dma.ifcr.write(|w| {
                    w.#cgif().clear()
                    .#ctcif().clear()
                    .#chtif().clear()
                    .#cteif().clear()
                });
            }

            #[inline(always)]
            pub fn is_complete(&self) -> bool {
                #dma.isr.read().#tcif().is_complete()
            }

            #[inline(always)]
            pub fn ndtr(&self) -> u16 {
                #dma.#ch.ndtr.read().ndt().bits()
            }
        }
    };

    TokenStream::from(expand)
}

// #[adc(ADC3_CH5_CH1_CH2_CH13_CH6_CH4_CH9_CH10)]
#[proc_macro_attribute]
pub fn adc(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let name = &input.ident;
    let attr = attr.to_string();

    let config = adc::parse(&attr);

    let adc = format_ident!("{}", config.name);
    let rcc_field = format_ident!("{}en", config.name.to_lowercase());
    let smpr = config.config_smpr();
    let chs = config.create_config_chan();
    let expand = quote! {
        #input

        impl #name {

            #[inline(always)]
            pub fn init(&self) {
                RCC.apb2enr.modify(|_, w| {
                    w.#rcc_field().enabled()
                });

                #adc.cr2.reset();
                #adc.cr1.write(|w| w.scan().enabled());
                #adc.cr2.write(|w| {
                    w.extsel().swstart()
                    .cont().continuous()
                    .dma().enabled()
                    .exttrig().enabled()
                });
                #smpr
                #chs

                #adc.cr2.modify(|_, w| {
                    w.adon().enabled()
                });
            
                #adc.cr2.modify(|_, w| w.rstcal().set_bit());
                while #adc.cr2.read().rstcal().bit_is_set() {}
                #adc.cr2.modify(|_, w| w.cal().set_bit());
                while #adc.cr2.read().cal().bit_is_set() {}
            }

            #[inline(always)]
            pub fn start(&self) {
                #adc.cr2.modify(|_, w| w.swstart().set_bit());
            }
        }
    };
    TokenStream::from(expand)
}

